package com.ljy.retry;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

/** 
* @author James
* @date 2018年8月23日 
*/
public class RetryProxy {
	
	private static Map<String,ClassData> classDataMap=new HashMap<>();
	
	private static ExecutorService executor=Executors.newSingleThreadExecutor();
	
	public static void main(String[] args) {
		Enhancer enhancer=new Enhancer();
		enhancer.setSuperclass(HelloWorldService.class);
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
				System.out.println("hello");
				Object result=proxy.invokeSuper(obj,args);
				return result;
			}
		});
		HelloWorldService service=(HelloWorldService) enhancer.create();
		String result=service.sayHello("James");
		System.out.println(result);
	}
	
	public static <T> T getObject(Class<T> clazz) {
		createClassData(clazz);
		
		Enhancer enhancer=new Enhancer();
		enhancer.setSuperclass(clazz);
		
		
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
				long start=System.currentTimeMillis();
				ClassData classData=classDataMap.get(clazz.getName());
				MethodData methodData=classData.getMethodDataMap().get(method);
				long expireTime=-1;
				int retryTimes=1;
				if(methodData.getIsHasRetryMethodAnnotation()!=null&&methodData.getIsHasRetryMethodAnnotation()==1) {
					expireTime=methodData.getExpireTime();
					retryTimes=methodData.getRetryTimes();
				}

				Callable<Object> c=new Callable<Object>() {
					@Override
					public Object call() throws Exception{
						try {
							return proxy.invokeSuper(obj, args);
						} catch (Throwable e) {
							e.printStackTrace();
							throw new Exception(e);
						}
					}
				};
				
				Future<Object> future=null;
				int count=0;
				Object result=null;
				do {
					future=executor.submit(c);
					if(expireTime==-1) {
						result=future.get();
					}else {
						try {
//							Thread.sleep(1000);
							result=future.get(expireTime, TimeUnit.MILLISECONDS);
						}catch(TimeoutException e) {
							e.printStackTrace();
							continue;
						}
					}
					if(result!=null) {
						System.out.println((System.currentTimeMillis()-start)+"ms");
						return result;
					}
				}while((++count)<retryTimes);
				System.out.println((System.currentTimeMillis()-start)+"ms");
				return result;
			}
		});
		return (T) enhancer.create();
	}
	
	private static void createClassData(Class clazz) {
		ClassData classData=new ClassData();
		classData.setClassName(clazz.getName());
		classData.setClazz(clazz);
		Object configurationAnn=clazz.getAnnotation(RetryConfiguration.class);
		if(configurationAnn==null) {
			return;
		}
		Method[] methodArr=clazz.getDeclaredMethods();
		Map<Method,MethodData> methodMap=new HashMap<>();
		classData.setMethodDataMap(methodMap);
		for(Method m:methodArr) {
			MethodData methodData=new MethodData();
			Annotation[] aArr=m.getAnnotations();
			for(Annotation a:aArr) {
				if(a instanceof RetryMethod) {
					methodData.setRetryTimes(((RetryMethod) a).retryTimes());
					methodData.setExpireTime(((RetryMethod) a).expireTime());
					methodData.setIsHasRetryMethodAnnotation(1);
					break;
				}
			}
			methodData.setParamClassArr(m.getParameterTypes());
			methodMap.put(m, methodData);
		}
		classDataMap.put(clazz.getName(), classData);
	}
	
	public static void shutdownExecutor() {
		executor.shutdown();
	}
	
}
